package top.suven.base.core.lock;

import org.springframework.beans.factory.annotation.Autowired;
import top.suven.base.core.redis.RedisClusterServer;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;

/** 
 * <pre> 
 * 基于Redis的SETNX操作实现的分布式锁 
 *  
 * 获取锁时最好用lock(long time, TimeUnit unit), 以免网路问题而导致线程一直阻塞 
 *  
 * <a href="http://redis.io/commands/setnx">SETNX操作参考资料</a> 
 * </pre> 
 *  
 * @author lixiaohui 
 * 
 */  
public class RedisBasedDistributedLock extends AbstractLock {

    @Autowired
    private RedisClusterServer jedis;
      
    private Date timeClient;
      
    // 锁的名字  
    protected String lockKey;  
      
    // 锁的有效时长(毫秒)  
    protected long lockExpires;  
      
    public RedisBasedDistributedLock( String lockKey, long lockExpires, long timeServerAddr) throws IOException {
        this.lockKey = lockKey;
        this.lockExpires = lockExpires;  
        timeClient = new Date(timeServerAddr);
    }  
      
    // 阻塞式获取锁的实现  
    protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt)
            throws InterruptedException{
        if (interrupt) {  
            checkInterruption();  
        }  
          
        // 超时控制 的时间可以从本地获取, 因为这个和锁超时没有关系, 只是一段时间区间的控制  
        long start = localTimeMillis();  
        long timeout = unit.toMillis(time); // if !useTimeout, then it's useless  
          
        while (useTimeout ? isTimeout(start, timeout) : true) {  
            if (interrupt) {  
                checkInterruption();  
            }  
              
            long lockExpireTime = serverTimeMillis() + lockExpires + 1;//锁超时时间
            String stringOfLockExpireTime = String.valueOf(lockExpireTime);  
              
            if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁
                // TODO 成功获取到锁, 设置相关标识  
                locked = true;  
                setExclusiveOwnerThread(Thread.currentThread());  
                return true;  
            }  
              
            String value = jedis.get(lockKey);  
            if (value != null && isTimeExpired(value)) { // lock is expired  
                // 假设多个线程(非单jvm)同时走到这里  
                String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); // getset is atomic  
                // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)  
                // 加入拿到的oldValue依然是expired的，那么就说明拿到锁了  
                if (oldValue != null && isTimeExpired(oldValue)) {  
                    // TODO 成功获取到锁, 设置相关标识  
                    locked = true;  
                    setExclusiveOwnerThread(Thread.currentThread());  
                    return true;  
                }  
            } else {   
                // TODO lock is not expired, enter next loop retrying  
            }  
        }  
        return false;  
    }  
      
    public boolean tryLock() {  
        long lockExpireTime = serverTimeMillis() + lockExpires + 1;//锁超时时间  
        String stringOfLockExpireTime = String.valueOf(lockExpireTime);  
          
        if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁  
            // TODO 成功获取到锁, 设置相关标识  
            locked = true;  
            setExclusiveOwnerThread(Thread.currentThread());  
            return true;  
        }  
          
        String value = jedis.get(lockKey);  
        if (value != null && isTimeExpired(value)) { // lock is expired  
            // 假设多个线程(非单jvm)同时走到这里  
            String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); // getset is atomic  
            // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)  
            // 假如拿到的oldValue依然是expired的，那么就说明拿到锁了  
            if (oldValue != null && isTimeExpired(oldValue)) {  
                // TODO 成功获取到锁, 设置相关标识  
                locked = true;  
                setExclusiveOwnerThread(Thread.currentThread());  
                return true;  
            }  
        } else {   
            // TODO lock is not expired, enter next loop retrying  
        }  
          
        return false;  
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    /** 
     * Queries if this lock is held by any thread. 
     *  
     * @return {@code true} if any thread holds this lock and 
     *         {@code false} otherwise 
     */  
    public boolean isLocked() {  
        if (locked) {  
            return true;  
        } else {  
            String value = jedis.get(lockKey);  
            // TODO 这里其实是有问题的, 想:当get方法返回value后, 假设这个value已经是过期的了,  
            // 而就在这瞬间, 另一个节点set了value, 这时锁是被别的线程(节点持有), 而接下来的判断  
            // 是检测不出这种情况的.不过这个问题应该不会导致其它的问题出现, 因为这个方法的目的本来就  
            // 不是同步控制, 它只是一种锁状态的报告.  
            return !isTimeExpired(value);  
        }  
    }  
  
    @Override  
    protected void unlock0() {  
        // TODO 判断锁是否过期  
        String value = jedis.get(lockKey);  
        if (!isTimeExpired(value)) {  
            doUnlock();  
        }  
    }  
      
    public void release() {  
        timeClient= null;
    }  
      
    private void doUnlock() {  
        jedis.del(lockKey);  
    }  
  
    private void checkInterruption() throws InterruptedException {  
        if(Thread.currentThread().isInterrupted()) {  
            throw new InterruptedException();  
        }  
    }  
      
    private boolean isTimeExpired(String value) {  
        // 这里拿服务器的时间来比较  
        return Long.parseLong(value) < serverTimeMillis();  
    }  
      
    private boolean isTimeout(long start, long timeout) {  
        // 这里拿本地的时间来比较  
        return start + timeout > System.currentTimeMillis();  
    }  
      
    private long serverTimeMillis(){  
        return timeClient.getTime();
    }  
      
    private long localTimeMillis() {  
        return System.currentTimeMillis();  
    }  
      
} 